#pragma once 
#include <string>
#include <iostream>
#include <mutex>
#include <fstream>
#include <algorithm>
#include <jsoncpp/json/json.h>
#include "oj_model.hpp"
#include "oj_view.hpp"
#include "../Common/Util.hpp"
#include "../Common/Log.hpp"
#include "../Common/httplib.h"

using namespace httplib;

//本次服务的一些相关配置
const std::string Conf = "./conf";


//一台机器的相关信息
class Machine{
public:
    Machine():_ip(""), _port(0), _load(0), _lock(nullptr){}
    ~Machine(){}
public:
    std::string _ip;     //机器的ip地址
    uint16_t _port;      //端口号
    uint64_t _load;      //负载情况

    //因为到时候可能一瞬间有大量的客户端同时请求编译服务，来一台机器，负载需要+1，但这会引发线程安全！
    std::mutex* _lock;   //c++内的mutex不支持拷贝，所以用一个指针
public:
    /*提升/降低主机负载*/
    void IncreaseLoad(){
        if(!_lock) return;
        std::lock_guard<std::mutex> lock(*_lock);
        ++_load;
    }

    void DecreaseLoad(){
        if(!_lock) return;
        std::lock_guard<std::mutex> lock(*_lock);
        --_load;
    }

    /*获取主机的负载*/
    uint64_t GetLoad(){
        if(!_lock) return -1;
        std::lock_guard<std::mutex> lock(*_lock);
        return _load;
    }

    void ClearLoad(){
        if(!_lock) return;
        std::lock_guard<std::mutex> lock(*_lock);
        _load = 0;
    }
};





//负载均衡模块
class LoadBalance{
public:
    LoadBalance(){
        assert(LoadAllMachine(Conf + "/service_machine.conf"));
        LOG(INFO) << "加载配置文件成功 : " << (Conf + "service_machine.conf") << "\n";
    }
    ~LoadBalance(){}

    /*把能提供服务的主机加载到内存*/
    bool LoadAllMachine(const std::string& server_machine_conf){
        std::ifstream in(server_machine_conf);
        if(!in.is_open()){
            LOG(FATAL) << "配置文件加载失败 : " << server_machine_conf << "\n";
            return false;
        }
        std::string line;
        while(std::getline(in, line)){
            if(line.size() == 0) continue;
            //读取出来的，是ip:port格式
            std::vector<std::string> tokens;
            StringUtil::StringSplit(line, &tokens, ":");
            if(tokens.size() != 2){
                LOG(WARNING) << "某台机器加载格式有误 : " << line << "\n";
                continue;
            }
            Machine m;
            m._ip = tokens[0];
            m._port = std::stoi(tokens[1]);
            m._lock = new std::mutex;
            //一开始加载的默认都是在线的，先让_online插入，这样一台主机的id就是0，刚好对应在_machines中的坐标
            _online.push_back(_machines.size());  
            _machines.push_back(m);          
        }
        in.close();
        return true;
    }

    /*选择一台机器，选择后，需要更新该主机的负载情况*/
    bool IntelligentSelection(int* id, Machine** m){
        std::lock_guard<std::mutex> guard(_mutex);
        int online_machine_num = _online.size();
        if(online_machine_num == 0) {
            LOG(FATAL) << "所有后端编译服务主机离线 : " << "请尽快维护" << "\n";
            return false;
        }

        // 使用轮询策略，找到当前负载最小的机器 这里可以手动遍历，也可以使用库函数
        auto min_id = std::min_element(_online.begin(), _online.end(), [this](const int& id1, const int& id2){
            return _machines[id1].GetLoad() < _machines[id2].GetLoad();
        });
        *id = *min_id;
        *m = &_machines[*min_id];
        return true;
    }

    /*机器的上线和下线*/
    void OnlineMachine(){
        std::lock_guard<std::mutex> guard(_mutex);
        _online.insert(_online.end(), _offline.begin(), _offline.end());
        LOG(INFO) << "已经恢复所有的主机" << "\n";
    }  

    void OfflineMachine(int id){
        std::lock_guard<std::mutex> guard(_mutex);
        //本质就是：把online中的id给去掉，加入到offline
        for(auto it = _online.begin(); it != _online.end(); ++it){
            if(*it == id){
                //这里会有一个问题：下线的时候，很可能此时还有负载！这会影响后续的流程
                _machines[id].ClearLoad();
                _online.erase(it);
                _offline.push_back(id);
                break;     /*直接break了，就不需要考虑迭代器失效的问题*/
            }
        }
    }

    /*就是用来进行测试用的*/
    void ShowOnlineMachine(){
        std::lock_guard<std::mutex> guard(_mutex);
        std::cout << "当前在线主机：" << std::endl;
        for(auto& id : _online){
            std::cout << " [ip : port: 主机id] : [" << _machines[id]._ip << " " << _machines[id]._port << " " << id << "]" << std::endl;
        }
        std::cout << "-------------------------------------------------------------------------------------------------" << std::endl;
        std::cout << "当前离线主机：" << std::endl;
        for(auto& id : _offline){
            std::cout << " [ip : port: 主机id] : [" << _machines[id]._ip << " " << _machines[id]._port << " " << id << "]" << std::endl;
        }
    }

private:
    //还需要管理所有的机器
    std::vector<Machine> _machines;    //但是，有些机器可能是不上线的，我们这里就通过machine在数组中的坐标来充当id
    std::vector<int> _online;          //在线机器
    std::vector<int> _offline;         //下线机器

    /*因为可能一瞬间会有很多主机在访问负载均衡模块，所以需要加个锁*/
    std::mutex _mutex;                 
};





//控制器模块
class Control{
public: 
    Control(){LOG(INFO) << "Control Module Create" << "\n";}
   ~Control(){LOG(INFO) << "Control Module Destroy" << "\n";}
public:
    /*让外界能够恢复所有的主机*/
    void RecoverAllMachine(){_load_balance.OnlineMachine();}

    /*获取题目列表的网页*/
    bool AllQuestion(std::string* out){
        std::vector<struct Question> qs; 
        //根据题目的编号进行升序排序
        bool ret = _model.GetAllQuestion(&qs);
        if(!ret) {
            std::string fail_html = "./wwwroot/404.html";
            FileUtil::ReadFromFile(fail_html, out, true);
            return false;
        }
        /*使用view模块拼接网页*/
        std::sort(qs.begin(), qs.end(), [](const struct Question& q1, const struct Question& q2){return std::stoi(q1._number) < std::stoi(q2._number);});
        _view.ExpandQuestionAll(qs, out);
        return true;
    }

    /*根据题号 获取到该题的网页*/
    bool Question(const std::string& number, std::string* out){
        struct Question q;
        bool ret = _model.GetOneQuestion(number, &q);
        if(!ret) {
            std::string fail_html = "./wwwroot/404.html";
            FileUtil::ReadFromFile(fail_html, out, true);
            return false;
        }
        /*使用view模块拼接网页*/
        _view.ExpandQuestionOne(q, out);
        return true;
    }

    void Judge(const std::string& number, const std::string& in_json, std::string* out_json){
        //0.通过题目编号获取细节
        struct Question q;
        _model.GetOneQuestion(number, &q);

        //1.获取到in_json内的代码部分,先做反序列化
        Json::Value in_value;
        Json::Reader reader;
        reader.parse(in_json, in_value);

        //2.拼接header代码，tail代码，形成真正能编译的代码
        Json::Value compile_value;
        compile_value["input"] = in_value["input"].asString();
        std::string real_code = in_value["code"].asString() + q._tail;
        compile_value["code"] = real_code;
        compile_value["cpu_limit"] = q._cpu_limit;
        compile_value["mem_limit"] = q._mem_limit;
        Json::StyledWriter writer;
        std::string compile_in_json = writer.write(compile_value);   /*真正调用编译服务的时候，还得传入json串*/
        
        

        //3.负载均衡地选择一台机器 然后执行编译服务
        /*逻辑是：因为不去确定今天选出来的是否成功，有很多特殊情况，所以我们这里需要特殊处理一下*/
        while(1){
            int id = 0;
            Machine* m = nullptr;
            if(_load_balance.IntelligentSelection(&id, &m)){
                LOG(INFO) << "连接到主机 [ip:port]: [" << m->_ip << " " << m->_port << "]\n"; 
                m->IncreaseLoad();

                //4.调用编译服务(http请求)
                Client cli(m->_ip, m->_port);
                if(auto res = cli.Post("/compile_and_run", compile_in_json, "application/json; charset=utf-8")){
                    //此时res就是一个装有应答信息或者是错误信息的一个类！
                    //5.结果赋值给out_json
                    if(res->status == 200) {
                        *out_json = res->body;
                        m->DecreaseLoad();
                        LOG(INFO) << "主机 [ip:port]: [" << m->_ip << " " << m->_port << "] 编译服务成功" << "\n";
                        break;
                    }   
                    else{
                        /*等待下次循环，重新选择主机*/
                        LOG(ERROR) << "主机 [ip:port]: [" << m->_ip << " " << m->_port << "] 请求编译服务错误" << "\n";
                        m->DecreaseLoad();
                    }
                }
                else{
                    //编译服务失败了 -> 可能是这台主机有问题
                    /*等待下一次选取主机*/
                    LOG(ERROR) << "主机 [ip:port]: [" << m->_ip << " " << m->_port << "] 请求服务失败 可能已经离线" << "\n"; 
                    m->DecreaseLoad();                  /*写不写无所谓*/
                    _load_balance.OfflineMachine(id);   /*下线这台机器*/
                    _load_balance.ShowOnlineMachine();  /*测试用的*/
                }
            }
            else break;
        }
    }

private:
    Model _model;
    View _view;
    LoadBalance _load_balance;
};